<?php
namespace GrouppaGroup;

require_once 'Controller.php';
require_once 'MessageFromApp.php';
require_once 'MessageFromServer.php';
require_once 'Group.php';

class GroupController extends Controller
{

    /**
     * receives a message from an application (override from controller)
     * @param  MessageFromApp    $message
     * @return MessageFromServer return message to the user
     */
    public function receiveMessage(MessageFromApp $message)
    {
        //CHECK: check access token
        if (!$this->checkAccess($message))
            return;
        //CHECK: handle received message
        if ($message->task) {
            switch ($message->task) {
                case 'joinGroup':
                    $this->joinGroup($message);
                    break;
                case 'createGroup':
                    $this->createGroup($message);
                    break;
                case 'leaveGroup':
                    $this->leaveGroup($message);
                    break;
                case 'getAllGroups':
                    $this->getAllGroups($message);
                    break;
                case 'getAllMembers':
                    $this->getAllMembers($message);
                    break;
                case 'getUserGroups':
                    $this->getUserGroups($message);
                    break;
                case 'changeGroupData':
                    $this->changeGroupData($message);
                    break;
                default:
                    self::unknownTask($message);
                    break;
            }
        }
    }
    /*
    ****************** Section: functions available to call for the applications
    */

    /**
     * Adds a group to the list of joined group of the user
     * @param  MessageFromApp $message message with username and group name and group password
     * @throws \Exception
     * @return boolean        true iff group joined
     */
    public function joinGroup(MessageFromApp $message)
    {
        //CHECK: create a new group with specified name and password
        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GroupController::joinGroup error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'groupName') || !property_exists($args, 'password') ||
            !property_exists($args, 'userName')) {
            throw new \Exception('GroupController::joinGroup error: message parameters didn\'t contain password, group name or user name');
        }
        $groupName = $args->groupName;
        $password = $args->password;
        $userName = $args->userName;

        $joined = $this->joinGroupReal(self::getUserId($userName), $groupName, $password);
        if (!$joined) {

            $serializedArguments = json_encode(array(
                'joined' => false,
                'reason' => 'Couldn\'t join the group, possibly the password is incorrect or the group doesn\'t exist.',
                'groupName' => $groupName,
                'pin'=>$password));

        } else {
            $serializedArguments = json_encode(array(
                'joined' => true,
                'reason' => '',
                'groupName' => $groupName,
                'pin'=>$password));

        }

        $result = $joined;

        $responseMessage = new MessageFromServer($serializedArguments, 'Group', $message->
            messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return $result;
    }

    /**
     *
     * @param  message $group name of the group to be created
     * @return Group   a newly created group, or NULL if nothing was created
     */
    /**
     * creates new group
     * @param  MessageFromApp $message a message with group name and password in serializedArguments
     * @throws \Exception
     * @return boolean        true iff group created
     */
    public function createGroup(MessageFromApp $message)
    {
        //CHECK: create a new group with specified name and password
        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GroupController::createGroup error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'groupName') || !property_exists($args, 'password') ||
            !property_exists($args, 'userName')) {
            throw new \Exception('GroupController::createGroup error: message parameters didn\'t contain password, group name or user name');
        }
        $groupName = $args->groupName;
        $password = $args->password;
        $userName = $args->userName;

        $db = Db::getInstance();
        $inserted = $db->sendInsertQuery('INSERT INTO groups ( group_name , group_password ) ' .
            "VALUES ('$groupName','$password');", 'groups');

        $groupId = $db->getLastId();
        $serializedArguments;
        if (!$inserted) {

            $serializedArguments = json_encode(array(
                'created' => false,
                'joined' => false,
                'manager' => false,
                'reason' => 'group name already taken',
                'groupName' => $groupName,
                'pin'=>$password));
        } else {
            $userId = self::getUserId($userName);
            $joined = $this->joinGroupReal($userId, $groupName, $password);
            if (!$joined) {

                $serializedArguments = json_encode(array(
                    'created' => true,
                    'joined' => false,
                    'manager' => false,
                    'reason' => 'group was created, but couldn\'t join it.',
                    'groupName' => $groupName,
                    'pin'=>$password));

            } else {
                $managerMade = $this->makeManager($userId, $groupId);
                $serializedArguments = json_encode(array(
                    'created' => true,
                    'joined' => true,
                    'manager' => $managerMade,
                    'reason' => '',
                    'groupName' => $groupName,
                    'pin'=>$password));

            }

        }
        $responseMessage = new MessageFromServer($serializedArguments, 'Group', $message->
            messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return $inserted && $joined;
    }

    /**
     * makes a user the manager of a group
     * @param  string  $userName
     * @param  string  $groupName
     * @return boolean true iff succeeded
     */
    private function makeManager($userId, $groupId)
    {

        $db = Db::getInstance();

        return $db->sendInsertQuery('INSERT INTO group_managers ( id_group , id_user )
                VALUES('.$groupId.','.$userId.') ;', 'group_managers');

    }

    /**
     * adds group to the list of joined groups of the user
     * @param  integer $userName
     * @param  string  $groupName
     * @param  string  $password
     * @return boolean did the join succeed?
     */
    private function joinGroupReal($userId, $groupName, $password)
    {

        $db = Db::getInstance();
        $result = $db->sendQuery("SELECT id_group FROM groups WHERE group_name='$groupName'" .
            " AND group_password='$password' ");
        if ($result->num_rows == 0) {
            return false;
        }
        $row = $result->fetch_row();
        $groupId = $row[0];
        $result->free();
        $db->sendInsertQuery('INSERT INTO usergroups ( id_user , id_group ) ' .
            "VALUES ($userId , $groupId )", 'usergroups');

        //the only reason above query may fail is because such input already exists there
        //in such case it's not bad, so we return always true
        return true;

    }

    /**
     * Removes a group from the list of user's joined group
     * @param  MessageFromApp $message message with the userName and groupName parameters
     * @throws \Exception
     * @return boolean        true iff the leaving succeeded
     */
    public function leaveGroup(MessageFromApp $message)
    {
        //CHECK: leave a specified group for current user
        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GroupController::leaveGroup error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'groupName') || !property_exists($args, 'userName')) {
            throw new \Exception('GroupController::leaveGroup error: message parameters didn\'t contain group name or user name');
        }
        $groupName = $args->groupName;
        $userName = $args->userName;

        $db = Db::getInstance();
        $result = $db->sendQuery('DELETE FROM usergroups WHERE usergroups.id_user IN ' .
            '(SELECT users.id_user FROM users WHERE users.user_name="' . $userName . '")' .
            ' AND id_group IN (SELECT groups.id_group FROM groups WHERE groups.group_name="' .
            $groupName . '");');

        $serializedArguments;
        if (!$result) {
            $serializedArguments = json_encode(array('left' => false, 'reason' =>
                    'the user wasn\'t joined with this group'));
        } else {
            $serializedArguments = json_encode(array('left' => true, 'reason' => ''));

        }
        $responseMessage = new MessageFromServer($serializedArguments, 'Group', $message->
            messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return $result;
    }

    /**
     * gets a list of all groups existing on a server
     * @param  MessageFromApp $message message from requesting client
     * @return boolean        true
     */
    public function getAllGroups(MessageFromApp $message)
    {
        //CHECK: get a list of all groups existing on a server

        $db = Db::getInstance();
        $result = $db->sendQuery('SELECT group_name , NOT(group_managers.id_user IS NULL) FROM groups
                LEFT JOIN (group_managers
                INNER JOIN users ON group_managers.id_user = users.id_user AND  users.user_name = "'.$message->from.'")
                ON groups.id_group = group_managers.id_group;');

        $groups = array();
        $n = $result->num_rows;
        for ($i = 0; $i < $n; $i++) {
            $row = $result->fetch_row();
            $groups[] = array('groupName'=>$row[0],'isManager'=>($row[1]?true:false));
        }

        $serializedArguments;
        if ($n == 0)
            $serializedArguments = '[]';
        else
            $serializedArguments = json_encode($groups);

        $responseMessage = new MessageFromServer($serializedArguments, 'Group', $message->
            messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return true;
    }

    /**
     * gets a list of all groups to which a user is signed on to
     * @param  MessageFromApp $message message from requesting client,
     *                                 with parameter 'userName' in it
     * @return boolean        true
     */
    public function getUserGroups(MessageFromApp $message)
    {
        //CHECK: get a list of all groups to which a user is signed on to

        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GroupController::getUserGroups error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'userName')) {
            throw new \Exception('GroupController::getUserGroups error: message parameters didn\'t contain user name');
        }
        $userName = $args->userName;

        $db = Db::getInstance();
        $result = $db->sendQuery('SELECT groups.group_name , NOT(group_managers.id_user IS NULL) , groups.group_password FROM groups
                INNER JOIN usergroups ON usergroups.id_group = groups.id_group
                INNER JOIN users AS u ON usergroups.id_user = u.id_user
                LEFT JOIN group_managers ON groups.id_group = group_managers.id_group AND group_managers.id_user = u.id_user
                WHERE u.user_name="'.$userName.'";');

        $groups = array();
        $n = $result->num_rows;
        for ($i = 0; $i < $n; $i++) {
            $row = $result->fetch_row();
            $groups[] = array('groupName'=>$row[0],
                    'isManager'=>($row[1]?true:false),
                    'pin'=>$row[2]);
        }

        $serializedArguments;
        if ($n == 0)
            $serializedArguments = '[]';
        else
            $serializedArguments = json_encode($groups);

        $responseMessage = new MessageFromServer($serializedArguments, 'Group', $message->
            messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return true;
    }

    /**
     * gets a list of all groups to which a user is signed on to
     * @param  MessageFromApp $message message from requesting client,
     *                                 with parameter 'groupName' in it
     * @return boolean        true
     */
    public function getGroupUsers(MessageFromApp $message)
    {
        //CHECK: get a list of all groups to which a user is signed on to

        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GroupController::getUserGroups error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'groupName')) {
            throw new \Exception('GroupController::getUserGroups error: message parameters didn\'t contain user name');
        }
        $groupName = $args->groupName;

        $db = Db::getInstance();
        $result = $db->sendQuery('SELECT user_name FROM groups WHERE id_group IN ' .
            '(SELECT id_group FROM usergroups WHERE id_user IN ' .
            '(SELECT id_user FROM users WHERE user_name="' . $userName . '" ));');

        $groups = array();
        $n = $result->num_rows;
        for ($i = 0; $i < $n; $i++) {
            $row = $result->fetch_row();
            $groups[] = $row[0];
        }

        $serializedArguments;
        if ($n == 0)
            $serializedArguments = '[]';
        else
            $serializedArguments = json_encode($groups);

        $responseMessage = new MessageFromServer($serializedArguments, 'Group', $message->
            messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return true;
    }
    /**
     * gets the list of members that is registered for a group
     * @param  MessageFromApp $message message from requesting client,
     *                                 with parameter 'groupName' in it
     * @return $allMembers    the name of the users that are in the Group
     */
    public function getAllMembers(MessageFromApp $message)
    {
        //CHECK: check if the message countains group name.
        if (!$message->serializedArguments || !is_a($message->serializedArguments, 'stdClass' )) {
            throw new \Exception('GroupController::getAllMembers error: message does not contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'groupName')) {
            throw new \Exception('GroupController::getAllMembers error: message parameters does not countain groupName');
        }

        //CHECK: query all the members in the group.
        $groupName = $args->groupName;
        $result = Db::getInstance() -> sendQuery('SELECT user_name , NOT (group_managers.id_user IS NULL) FROM users
                INNER JOIN usergroups ON users.id_user = usergroups.id_user
                INNER JOIN groups ON usergroups.id_group = groups.id_group
                LEFT JOIN group_managers ON usergroups.id_group = group_managers.id_group AND  usergroups.id_user = group_managers.id_user
                WHERE group_name="'.$groupName.'"; ');

        //CHECK: output the members in the json format put it in the arguement from the message from server format.
        $n = $result->num_rows;
        $members = array();
        for ($i=0;$i < $n;$i++) {
            $row = $result->fetch_row();
            $members[] = array('userName'=>$row[0],'isManager'=>($row[1]?true:false));
        }
        $serializedArguments;
        if($n==0)
            $serializedArguments = '[]';
        else
            $serializedArguments = json_encode(array(
                    'members'=>$members,
                    'groupName'=>$groupName
        ));

        $responseMessage = new MessageFromServer($serializedArguments,'Members', $message->messageType, $message->id);
        ApplicationTCPConnection::sendMessage($responseMessage);

        return true;

    }
    /**
     * changes some information about the group, like password
     * @param  MessageFromApp $message message from requesting client,
     *                                 with parameter 'groupName' in it and other optional parameters like:
     *                                 'newPin'
     * @return boolean        true iff succeeded to change the data
     */
    public function changeGroupData(MessageFromApp $message)
    {
        //CHECK: check if the message countains group name.
        if (!$message->serializedArguments || !is_a($message->serializedArguments, 'stdClass' )) {
            throw new \Exception('GroupController::changeGroupData error: message does not contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'groupName')) {
            throw new \Exception('GroupController::changeGroupData error: message parameters does not contain groupName');
        }

        if (!property_exists($args,'newPin')) {
            throw new \Exception('GroupController::changeGroupData error: message parameters does not contain new pin');

        }
        $newPin = $args->newPin;
        $groupName = $args->groupName;

        $success;
        $errorReason;
        if (!self::isManager($groupName, $message->from)) {
            $success = false;
            $errorReason = "You are not the manager of group $groupName";
        } else {

            $success = Db::getInstance() -> sendUpdateQuery('UPDATE groups SET group_password="'.$newPin.'" WHERE group_name="'.$groupName.'"');
            $errorReason = "Group $groupName doesn't exist";
        }

        $serializedArguments = json_encode(array(
            'succeeded'=>($success?true:false),
            'errorReason'=>$errorReason,
            'newPin'=>$newPin
        ));

        //TODO: new pin should be sent to other members of the group
        $responseMessage = new MessageFromServer($serializedArguments,'Members', $message->messageType, $message->id);
        ApplicationTCPConnection::sendMessage($responseMessage);

        return $success;

    }
}
